﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Security.Authentication;
using System.Net.Sockets;
using System.Net.Security;
using System.IO;
using System.Threading;
using System.Runtime.Remoting.Messaging;
using System.Diagnostics;
using System.Collections.Concurrent;

namespace AllTalkProxy
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.
    public class AllTalkProxyService : IAllTalkProxyService
    {
        public static ConcurrentDictionary<Jid, ConnectionManager> ActiveSessions = new ConcurrentDictionary<Jid, ConnectionManager>();

        public AllTalkProxyService()
        {
            if (Debugger.IsAttached)
            {
                Constants.BindTimeout = 999999999;
                Constants.ReceiveTimeout = 9999999;
            }
        }

        public ConnectionResult Connect(ConnectionParameters connectionParams)
        {
            ConnectionManager manager;
            manager = new ConnectionManager(ServerConfiguration.ChatServer,
                ServerConfiguration.Port,
                UserConfiguration.Username,
                UserConfiguration.Password);
            manager.Connect();
            ActiveSessions.AddOrUpdate(manager.Jid, manager, (jid, mgr) => manager);
            string rosterXml;
            manager.GetRooster(out rosterXml);
                        
            return new ConnectionResult { Jid = manager.Jid.Value, RosterXml = rosterXml, PresenceXml = manager.GetPresence() };
        }

        public RemoteCertificateValidationCallback ValidateServerCertificate { get; set; }


        public void Send(string senderJid, string xmlStaza)
        {
            ConnectionManager manager = ActiveSessions[new Jid(senderJid)];
            manager.Send(xmlStaza);
        }


        public IAsyncResult BeginReceiveMessagesAndPresence(string senderJid, AsyncCallback callback, object state)
        {
            ConnectionManager manager = ActiveSessions[new Jid(senderJid)];
            ServerUpdateAsyncResult asyncResult = new ServerUpdateAsyncResult(callback, state, manager);
            RegisteredWaitHandle waitHandle = ThreadPool.RegisterWaitForSingleObject(
                manager.DataReceivedEvent, 
                new WaitOrTimerCallback(MessageOrPresenceReceivedCallback), 
                asyncResult,
                Constants.SendReceiveTimeout, 
                /*executeOnlyOnce*/true);

            return asyncResult;
        }

        public ServerUpdate EndReceiveMessagesAndPresence(IAsyncResult asyncResult)
        {
            ServerUpdateAsyncResult serverUpdateAsyncResult = (ServerUpdateAsyncResult)asyncResult;
            if (!asyncResult.IsCompleted)
            {
                asyncResult.AsyncWaitHandle.WaitOne();
            }
            return serverUpdateAsyncResult.ServerUpdate;
        }

        public void MessageOrPresenceReceivedCallback(object state, bool timeout)
        {
            ServerUpdateAsyncResult serverUpdateAsyncResult = (ServerUpdateAsyncResult)state; 
            try
            {
                serverUpdateAsyncResult.ServerUpdate = new ServerUpdate();
                serverUpdateAsyncResult.ServerUpdate.PresenceXml = serverUpdateAsyncResult.ConnectionManger.GetPresence();
                serverUpdateAsyncResult.ServerUpdate.MessageXml = serverUpdateAsyncResult.ConnectionManger.GetMessages();
                serverUpdateAsyncResult.ServerUpdate.VcardXml = serverUpdateAsyncResult.ConnectionManger.GetVcards();
            }
            finally
            {
                serverUpdateAsyncResult.OnCompleted();
            }
        }


        public void GetVcard(string senderJid, string[] friends)
        {
            ConnectionManager manager = ActiveSessions[new Jid(senderJid)];
            foreach(string friend in friends)
            {
                manager.GetVCard(friend);
            }
        }
    }

    internal class ServerUpdateAsyncResult : IAsyncResult
    {
        AsyncCallback _callback;
        object _state;
        EventWaitHandle _asyncWaitHandle;

        public ServerUpdate ServerUpdate { get; set; }

        public ConnectionManager ConnectionManger { get; private set; }

        public ServerUpdateAsyncResult(AsyncCallback callback, object state, ConnectionManager manager)
        {
            _callback = callback;
            _state = state;
            this.ConnectionManger = manager;
        }


        public object AsyncState
        {
            get { return _state; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get 
            {
                if (_asyncWaitHandle == null)
                {
                    _asyncWaitHandle = new ManualResetEvent(false);
                }
                return _asyncWaitHandle;
            }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get;
            private set;
        }

        public virtual void OnCompleted()
        {
            ((ManualResetEvent)this.AsyncWaitHandle).Set();
            this.IsCompleted = true;
            if (_callback != null)
            {
                _callback(this);
            }
        }
    }
}
